// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fcntl.h>

#include <new>

#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <fbl/function.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/mutex.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <fbl/string.h>
#include <fbl/string_buffer.h>
#include <fbl/string_piece.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/namespace.h>
#include <lib/zx/channel.h>
#include <zircon/device/vfs.h>
#include <zircon/processargs.h>

#include "../private.h"
#include "local-connection.h"
#include "local-filesystem.h"
#include "local-vnode.h"

namespace fio = ::llcpp::fuchsia::io;

namespace {

class DirentFiller {
 public:
  explicit DirentFiller(void* buffer, size_t length)
      : start_(buffer), buffer_(buffer), length_(length) {}

  zx_status_t Add(const char* name, size_t len, uint32_t type) {
    size_t sz = sizeof(vdirent_t) + len;

    if (sz > length_ || len > NAME_MAX) {
      return ZX_ERR_INVALID_ARGS;
    }
    vdirent_t* de = static_cast<vdirent_t*>(buffer_);
    de->ino = fio::INO_UNKNOWN;
    de->size = static_cast<uint8_t>(len);
    de->type = static_cast<uint8_t>(type);
    memcpy(de->name, name, len);

    buffer_ = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(buffer_) + sz);
    length_ -= sz;
    return ZX_OK;
  }

  size_t Used() const {
    return reinterpret_cast<uintptr_t>(buffer_) - reinterpret_cast<uintptr_t>(start_);
  }

 private:
  void* start_;
  void* buffer_;
  size_t length_;
};

struct ExportState {
  // The minimum size of flat namespace which will contain all the
  // information about this |fdio_namespace|.
  size_t bytes;
  // The total number of entries (path + handle pairs) in this namespace.
  size_t count;
  // A (moving) pointer to start of the next path.
  char* buffer;
  zx_handle_t* handle;
  uint32_t* type;
  char** path;
};

zx_status_t ValidateName(const fbl::StringPiece& name) {
  if ((name.length() == 0) || (name.length() > NAME_MAX)) {
    return ZX_ERR_INVALID_ARGS;
  }
  if (name == fbl::StringPiece(".") || name == fbl::StringPiece("..")) {
    return ZX_ERR_INVALID_ARGS;
  }
  return ZX_OK;
}

}  // namespace

fdio_namespace::fdio_namespace() : root_(LocalVnode::Create(nullptr, zx::channel(), "")) {}

fdio_namespace::~fdio_namespace() {
  fbl::AutoLock lock(&lock_);
  root_->Unlink();
}

zx_status_t fdio_namespace::WalkLocked(fbl::RefPtr<const LocalVnode>* in_out_vn,
                                       const char** in_out_path) const {
  fbl::RefPtr<const LocalVnode> vn = *in_out_vn;
  const char* path = *in_out_path;

  // Empty path or "." matches initial node.
  if ((path[0] == 0) || ((path[0] == '.') && (path[1] == 0))) {
    return ZX_OK;
  }

  for (;;) {
    // Find the next path segment.
    const char* name = path;
    const char* next = strchr(path, '/');
    size_t len = next ? static_cast<size_t>(next - path) : strlen(path);

    // Path segments may not be empty.
    if (len == 0) {
      return ZX_ERR_BAD_PATH;
    }

    fbl::RefPtr<LocalVnode> child = vn->Lookup(fbl::StringPiece(name, len));
    if (child == nullptr) {
      // If no child exists with this name, we either failed to lookup a node,
      // or we must transmit this request to the remote node.
      if (!vn->Remote().is_valid()) {
        return ZX_ERR_NOT_FOUND;
      }

      *in_out_vn = vn;
      *in_out_path = path;
      return ZX_OK;
    }

    vn = child;
    if (!next) {
      // Lookup has completed successfully for all nodes, and no path remains.
      // Return the requested local node.
      *in_out_vn = vn;
      *in_out_path = ".";
      return ZX_OK;
    }

    // Lookup completed successfully, but more segments exist.
    path = next + 1;
  }
}

zx_status_t fdio_namespace::Open(fbl::RefPtr<const LocalVnode> vn, const char* path, uint32_t flags,
                                 uint32_t mode, fdio_t** out) const {
  {
    fbl::AutoLock lock(&lock_);
    zx_status_t status = WalkLocked(&vn, &path);
    if (status != ZX_OK) {
      return status;
    }

    if (!vn->Remote().is_valid()) {
      // The Vnode exists, but it has no remote object. Open a local reference.
      if ((*out = CreateConnection(vn)) == nullptr) {
        return ZX_ERR_NO_MEMORY;
      }
      return ZX_OK;
    }
  }

  // If we're trying to mkdir over top of a mount point,
  // the correct error is EEXIST
  if ((flags & ZX_FS_FLAG_CREATE) && !strcmp(path, ".")) {
    return ZX_ERR_ALREADY_EXISTS;
  }

  // Active remote connections are immutable, so referencing remote here
  // is safe. We don't want to do a blocking open under the ns lock.
  return fdio_remote_open_at(vn->Remote().get(), path, flags, mode, out);
}

zx_status_t fdio_namespace::Readdir(const LocalVnode& vn, void* buffer, size_t length,
                                    size_t* out_actual) const {
  fbl::AutoLock lock(&lock_);
  DirentFiller dirents(buffer, length);
  if (dirents.Add(".", 1, VTYPE_TO_DTYPE(V_TYPE_DIR)) != ZX_OK) {
    *out_actual = 0;
    return ZX_OK;
  }
  vn.ForAllChildren([&dirents](const LocalVnode& vn) {
    return dirents.Add(vn.Name().data(), vn.Name().length(), VTYPE_TO_DTYPE(V_TYPE_DIR));
  });
  *out_actual = dirents.Used();
  return ZX_OK;
}

fdio_t* fdio_namespace::CreateConnection(fbl::RefPtr<const LocalVnode> vn) const {
  return fdio_internal::CreateLocalConnection(fbl::WrapRefPtr(this), std::move(vn));
}

zx_status_t fdio_namespace::Connect(const char* path, uint32_t flags, zx::channel channel) const {
  // Require that we start at /
  if (path[0] != '/') {
    return ZX_ERR_NOT_FOUND;
  }
  path++;

  fbl::RefPtr<const LocalVnode> vn;
  {
    fbl::AutoLock lock(&lock_);
    vn = root_;
    zx_status_t status = WalkLocked(&vn, &path);
    if (status != ZX_OK) {
      return status;
    }

    // cannot connect via non-mountpoint nodes
    if (!vn->Remote().is_valid()) {
      return ZX_ERR_NOT_SUPPORTED;
    }
  }

  return fdio_open_at(vn->Remote().get(), path, flags, channel.release());
}

zx_status_t fdio_namespace::Unbind(const char* path) {
  if ((path == nullptr) || (path[0] != '/')) {
    return ZX_ERR_INVALID_ARGS;
  }

  // Skip leading slash.
  path++;

  if (path[0] == 0) {
    // The path was "/" so we're trying to unbind to the root vnode.
    return ZX_ERR_NOT_SUPPORTED;
  }

  fbl::AutoLock lock(&lock_);
  fbl::RefPtr<LocalVnode> vn = root_;
  // If we remove a vnode, we may create one or more childless intermediate parent nodes.
  // This node denotes the "highest" such node in the filesystem hierarchy.
  fbl::RefPtr<LocalVnode> removable_origin_vn;

  for (;;) {
    const char* next = strchr(path, '/');
    fbl::StringPiece name(path, next ? (next - path) : strlen(path));
    zx_status_t status = ValidateName(name);
    if (status != ZX_OK) {
      return status;
    }

    if (vn->Remote().is_valid()) {
      // Since shadowing is disallowed, this must refer to an invalid path.
      return ZX_ERR_NOT_FOUND;
    }

    vn = vn->Lookup(name);
    if (vn == nullptr) {
      return ZX_ERR_NOT_FOUND;
    }

    size_t children_count = 0;
    vn->ForAllChildren([&children_count](const LocalVnode& vn) {
      if (++children_count > 1) {
        return ZX_ERR_STOP;
      }
      return ZX_OK;
    });

    if (children_count > 1) {
      // If this node has multiple children (including something OTHER than the node
      // we're potentially unbinding), we shouldn't try to remove it while deleting
      // childless intermediate nodes.
      removable_origin_vn = nullptr;
    } else if (removable_origin_vn == nullptr) {
      // If this node has one or fewer children, it's a viable candidate for removal.
      // Only set this if it's the "highest" node we've seen satisfying this property.
      removable_origin_vn = vn;
    }

    if (!next) {
      // This is the last segment; we must match.
      if (!vn->Remote().is_valid()) {
        return ZX_ERR_NOT_FOUND;
      }
      // This assertion must hold without shadowing: |vn| should
      // have no children, so at minimum, |removable_origin_vn| = |vn|.
      ZX_DEBUG_ASSERT(removable_origin_vn != nullptr);
      removable_origin_vn->Unlink();
      return ZX_OK;
    }

    path = next + 1;
  }
}

zx_status_t fdio_namespace::Bind(const char* path, zx::channel remote) {
  if (!remote.is_valid()) {
    return ZX_ERR_BAD_HANDLE;
  }
  if ((path == nullptr) || (path[0] != '/')) {
    return ZX_ERR_INVALID_ARGS;
  }

  // Skip leading slash.
  path++;

  fbl::AutoLock lock(&lock_);
  fbl::RefPtr<LocalVnode> vn = root_;
  if (path[0] == 0) {
    // The path was "/" so we're trying to bind to the root vnode.
    return vn->SetRemote(std::move(remote));
  }

  zx_status_t status = ZX_OK;
  fbl::RefPtr<LocalVnode> first_new_node = nullptr;

  // If we fail, but leave any intermediate nodes, we need to clean them up
  // before unlocking and returning.
  auto cleanup = fbl::MakeAutoCall([&first_new_node]() {
    if (first_new_node != nullptr) {
      first_new_node->Unlink();
    }
  });

  for (;;) {
    const char* next = strchr(path, '/');
    fbl::StringPiece name(path, next ? (next - path) : strlen(path));
    status = ValidateName(name);
    if (status != ZX_OK) {
      return status;
    }

    if (vn->Remote().is_valid()) {
      // Shadowing is disallowed.
      return ZX_ERR_NOT_SUPPORTED;
    }

    if (next) {
      // Not the final segment.
      fbl::RefPtr<LocalVnode> child = vn->Lookup(name);
      if (child == nullptr) {
        // Create a new intermediate node.
        vn = LocalVnode::Create(vn, zx::channel(), fbl::String(name));

        // Keep track of the first node we create. If any subsequent
        // operation fails during bind, we will need to delete all nodes
        // in this subtree.
        if (first_new_node == nullptr) {
          first_new_node = vn;
        }
      } else {
        // Re-use an existing intermediate node.
        vn = child;
      }
      path = next + 1;
    } else {
      // Final segment. Create the leaf vnode and stop.
      if (vn->Lookup(name) != nullptr) {
        return ZX_ERR_ALREADY_EXISTS;
      }
      vn = LocalVnode::Create(vn, std::move(remote), fbl::String(name));
      break;
    }
  }

  cleanup.cancel();
  return ZX_OK;
}

fdio_t* fdio_namespace::OpenRoot() const {
  fbl::AutoLock lock(&lock_);
  if (!root_->Remote().is_valid()) {
    return CreateConnection(root_);
  }

  // Borrow a reference to root's |remote| connection.
  //
  // We may safely access this member after unlocking because:
  // - Remotes are immutable on LocalVnodes once they have been set (immutability is
  // guaranteed).
  // - fdio_namespace holds a strong reference to |root_| for the duration of this
  // method (lifetime is guaranteed).
  const zx::channel& remote = root_->Remote();
  lock.release();

  fdio_t* io;
  zx_status_t status = fdio_remote_open_at(remote.get(), "", O_RDWR, 0, &io);
  if (status != ZX_OK) {
    return nullptr;
  }
  return io;
}

zx_status_t fdio_namespace::Export(fdio_flat_namespace_t** out) const {
  ExportState es;
  es.bytes = sizeof(fdio_flat_namespace_t);
  es.count = 0;

  fbl::AutoLock lock(&lock_);

  auto count_callback = [&es](const fbl::StringPiece& path, const zx::channel& channel) {
    // Each entry needs one slot in the handle table,
    // one slot in the type table, and one slot in the
    // path table, plus storage for the path and NUL
    es.bytes += sizeof(zx_handle_t) + sizeof(uint32_t) + sizeof(char**) + path.length() + 1;
    es.count += 1;
    return ZX_OK;
  };
  fdio_internal::EnumerateRemotes(*root_, count_callback);

  fdio_flat_namespace_t* flat = static_cast<fdio_flat_namespace_t*>(malloc(es.bytes));
  if (flat == nullptr) {
    return ZX_ERR_NO_MEMORY;
  }
  // We've allocated enough memory for the flat struct
  // followed by count handles, followed by count types,
  // followed by count path ptrs followed by enough bytes
  // for all the path strings.  Point es.* at the right
  // slices of that memory:
  es.handle = reinterpret_cast<zx_handle_t*>(flat + 1);
  es.type = reinterpret_cast<uint32_t*>(es.handle + es.count);
  es.path = reinterpret_cast<char**>(es.type + es.count);
  es.buffer = reinterpret_cast<char*>(es.path + es.count);
  es.count = 0;

  auto export_callback = [&es](const fbl::StringPiece& path, const zx::channel& channel) {
    zx::channel remote(fdio_service_clone(channel.get()));
    if (!remote.is_valid()) {
      return ZX_ERR_BAD_STATE;
    }
    strlcpy(es.buffer, path.data(), path.length() + 1);
    es.path[es.count] = es.buffer;
    es.handle[es.count] = remote.release();
    es.type[es.count] = PA_HND(PA_NS_DIR, static_cast<uint32_t>(es.count));
    es.buffer += (path.length() + 1);
    es.count++;
    return ZX_OK;
  };

  zx_status_t status = fdio_internal::EnumerateRemotes(*root_, export_callback);
  lock.release();

  if (status != ZX_OK) {
    zx_handle_close_many(es.handle, es.count);
    free(flat);
  } else {
    flat->count = es.count;
    flat->handle = es.handle;
    flat->type = es.type;
    flat->path = (const char* const*)es.path;
    *out = flat;
  }

  return status;
}
